﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Security.Cryptography;
using Xin478.MySocket;

namespace Xin478.CSWork.Common
{
    public class TransferFileHelper
    {
        private Dictionary<string, TransferFileInfo> transferFiles = new Dictionary<string, TransferFileInfo>();
        private Queue<string> waitList = new Queue<string>(23);
        static object lockHelper = new object();
        const string BEGINTAG = "@@@";
        static readonly byte[] tag = System.Text.Encoding.UTF8.GetBytes(BEGINTAG);

        private byte maxWorkingNum = 5;
        private byte runningNum = 0;
        private int readBufferSize = 1024 * 31;
        private int writeBufferSize = 1024 * 64;

        public int ReadSize { get { return readBufferSize; } set { readBufferSize = value; } }
        public int WriteBufferSize { get { return writeBufferSize; } set { writeBufferSize = value; } }
        public byte MaxWorkingNum { get { return maxWorkingNum; } set { maxWorkingNum = value; } }

        public delegate void TransferHandler(TransferFileInfo transferFileInfo);
        public delegate void TransferCancelHandler(TransferFileInfo transferFileInfo,ref bool canceled);
        public event TransferHandler StartTransferHandler;
        public event TransferCancelHandler AddingTransferHandler;
        public event TransferHandler AddTransferHandler;
        public event TransferHandler TransferBlockHandler;
        public event TransferHandler FinishTransferHandler;
        //public event TransferHandler StartFinishTransferHandler;
        public delegate void SendInvoke(Session recvDataClient, byte[] datagram);
        public SendInvoke sendInvoke;

        //文件数据包,头部长度60,@@@md5.数据从索引50开始即51号元素开始
        public void DatagramReceive(Session session, ref bool cancel)
        {
            cancel = Filter(session);
        }

        bool Filter(Session session)
        {
            int j = 0, i = 0,k=0;
            while (session.Datagram[i++] == tag[j])
            {
                if (++j == tag.Length) break;
            }
            if (i==1) return false;

            j = 0;
            k = i;
            for(;k<session.Datagram.Length;k++)
            {
                if (session.Datagram[k] == tag[j])
                {
                    if (++j == tag.Length)
                        break;
                }
                else if (j > 0) j = 0;
            }
            if (j != tag.Length) return false;
            k++;

            string cmd = System.Text.Encoding.UTF8.GetString(session.Datagram, i, k-tag.Length-i);
            string[] header = cmd.Trim().Split('|');

            switch (header[0])
            {
                case "SENDNEXT": SendNextBlock(header[1], session); break;
                case "GETNEXT": GetNextBlock(header[1], session,k); break;
                case "FINISH": FinishTransfer(header[1]); break;
                case "STARTDOWN": StartDownLoad(header[1], header[2], header[3], header[4], Int32.Parse(header[5]), session); break;
                case "KILL": KillUpLoad(header[1],header[2]=="1"); break;
            }
            return true;
        }

        //开始初始化接受的文件并尝试请求下载
        void StartDownLoad(string guid, string md5, string tag,string filename,int filesize,Session session)
        {
            if (this.AddingTransferHandler == null) throw new ArgumentException("类的AddingTransferHandler不能为空");

            TransferFileInfo tfile = new TransferFileInfo();
            tfile.Status = TransferFileStatus.DownLoad;
            tfile.Guid = guid;
            tfile.MD5 = md5;
            tfile.Tag = tag;
            tfile.Session = session;
            tfile.FileName = filename;
            tfile.FileSize = filesize;

            bool cancel = false;
            this.AddingTransferHandler(tfile, ref cancel);
            if (cancel)
            {
                this.sendInvoke(session, System.Text.Encoding.UTF8.GetBytes(BEGINTAG + "KILL|" + (tfile.IsFail ? "0" : "1") + "|" + tfile.Guid + BEGINTAG));
                return;
            }

            if (tfile.FileStream == null)
            {
                if (!Directory.Exists(tfile.TempFileDir))
                {
                    Directory.CreateDirectory(tfile.TempFileDir);
                }
                if (File.Exists(tfile.TempFileDir + tfile.Guid)) File.Delete(tfile.TempFileDir + tfile.Guid);

                tfile.FileStream = File.Open(tfile.TempFileDir + tfile.Guid, FileMode.Append);

            }
            this.transferFiles.Add(guid, tfile);

            if (this.AddTransferHandler != null) this.AddTransferHandler(tfile);

            this.StartDownLoad(tfile);
        }
        //发送下一文件区块
        void SendNextBlock(string guid, Session session)
        {
            if (!this.transferFiles.ContainsKey(guid)) 
                return;
            TransferFileInfo tfile = this.transferFiles[guid];


            if (tfile.FileStream == null || tfile.FileStream.Position == tfile.FileSize)
            {
                if (tfile.FileStream != null)
                {
                    tfile.FileStream.Close();
                    tfile.FileStream.Dispose();
                }
                lock (lockHelper)
                {
                    runningNum--;
                    tfile.InProcess = false;
                }
                this.transferFiles.Remove(guid);
                //发布结束
                
                this.sendInvoke(tfile.Session, System.Text.Encoding.UTF8.GetBytes(BEGINTAG + "FINISH|" + tfile.Guid + BEGINTAG));

                if (this.FinishTransferHandler != null) this.FinishTransferHandler(tfile);

                this.TransferWait();

            }
            else
            {
                byte[] cmd = System.Text.Encoding.UTF8.GetBytes(BEGINTAG + "GETNEXT|" + tfile.Guid + BEGINTAG);
                byte[] data;

                int j = tfile.FileStream.Read(tfile.Buffer, 0, tfile.Buffer.Length);
                tfile.TransferSize += j;
                data = new byte[j + cmd.Length];

                Array.Copy(cmd, 0, data, 0, cmd.Length);
                Array.Copy(tfile.Buffer, 0, data,  cmd.Length,j);

                this.sendInvoke(tfile.Session, data);

                //发布发送区块
                if (this.TransferBlockHandler != null) this.TransferBlockHandler(tfile);

            }

        }
        //接受下一文件区块
        void GetNextBlock(string guid, Session session, int offset)
        {
            if (!this.transferFiles.ContainsKey(guid)) 
                return;
            TransferFileInfo tfile = this.transferFiles[guid];
            if (tfile == null || tfile.FileStream == null) 
                return;
            int j= session.Datagram.Length-offset;
            tfile.FileStream.Write(session.Datagram, offset,j);
            tfile.TransferSize += j;

            this.sendInvoke(tfile.Session, System.Text.Encoding.UTF8.GetBytes(BEGINTAG + "SENDNEXT|" + guid + BEGINTAG));

            if (this.TransferBlockHandler != null) this.TransferBlockHandler(tfile);
        }
        //开始上传一个文件
        void StartUpLoad(TransferFileInfo transferFileInfo)
        {
            string cmd = BEGINTAG + "STARTDOWN|" + transferFileInfo.Guid + "|" + transferFileInfo.MD5 + "|" + transferFileInfo.Tag + "|" + transferFileInfo.FileName + "|" + transferFileInfo.FileSize + BEGINTAG;
            this.sendInvoke(transferFileInfo.Session, System.Text.Encoding.UTF8.GetBytes(cmd));
            if(this.StartTransferHandler!=null) this.StartTransferHandler(transferFileInfo);
        }
        //开始下载一个文件
        void StartDownLoad(TransferFileInfo transferFileInfo)
        {            
            this.sendInvoke(transferFileInfo.Session, System.Text.Encoding.UTF8.GetBytes(BEGINTAG + "SENDNEXT|" + transferFileInfo.Guid + BEGINTAG));
            if (this.StartTransferHandler != null) this.StartTransferHandler(transferFileInfo);
        }
        //结束文件传输
        void FinishTransfer(string guid)
        {
            if (!this.transferFiles.ContainsKey(guid)) 
                return;
            TransferFileInfo tfile = this.transferFiles[guid];

            lock (lockHelper)
            {
                runningNum--;
            }
            if (tfile.Status == TransferFileStatus.DownLoad)
            {
                tfile.FileStream.Flush();
                tfile.FileStream.Close();
                tfile.FileStream.Dispose();
                FileStream fs = File.OpenRead(tfile.TempFileDir + tfile.Guid);
                MD5CryptoServiceProvider get_md5 = new MD5CryptoServiceProvider();
                byte[] hash_byte = get_md5.ComputeHash(fs);
                fs.Close();
                fs.Dispose();

                string md5 = BitConverter.ToString(hash_byte).Replace("-", "");
                if (string.IsNullOrEmpty(tfile.ToFileName)) tfile.ToFileName = tfile.FileName;

                if (string.Compare(md5, tfile.MD5) == 0)
                {
                    if (File.Exists(tfile.FileDir + tfile.ToFileName))
                    {
                        int i = tfile.ToFileName.LastIndexOf('.');
                        string ext,name;
                        if (i == -1)
                        {
                            ext = "";
                            name = tfile.ToFileName;
                        }
                        else
                        {
                            ext='.'+tfile.ToFileName.Substring(i + 1);
                            name=tfile.ToFileName.Substring(0, i);
                        }
                        i = 1;
                        string _name=tfile.FileDir + name+"({0})"+ext;
                        while (File.Exists(string.Format(_name, i))) i++;

                        File.Move(tfile.TempFileDir + tfile.Guid, string.Format(_name, i));
                    }else
                        File.Move(tfile.TempFileDir + tfile.Guid, tfile.FileDir + tfile.ToFileName);

                    this.transferFiles.Remove(guid);
                    tfile.IsFail = false;
                }
                else
                {
                    File.Delete(tfile.TempFileDir + tfile.Guid);
                    tfile.IsFail = true;
                }
            }

            if(this.FinishTransferHandler!=null)            this.FinishTransferHandler(tfile);

            this.TransferWait();
        }
        //取消上次进程
        void KillUpLoad(string guid, bool issuccess)
        {
            if (!this.transferFiles.ContainsKey(guid))
                return;

            TransferFileInfo tfile = this.transferFiles[guid];
            KillProcess(tfile, issuccess);

        }

        public void TransferWait()
        {
            string guid = null;
            lock (lockHelper)
            {
                if (runningNum <= maxWorkingNum && waitList.Count>0)
                {
                    guid = waitList.Dequeue();
                    runningNum++;
                }
            }
            if (guid != null)
            {
                TransferFileInfo transferFileInfo = this.transferFiles[guid];
                transferFileInfo.BeginTime = DateTime.Now;
                transferFileInfo.InProcess = true;
                if (transferFileInfo.Status == TransferFileStatus.DownLoad) this.StartDownLoad(transferFileInfo);
                else this.StartUpLoad(transferFileInfo);
            }
        }

        public void KillProcess(TransferFileInfo transferFileInfo, bool success)
        {
            if (!this.transferFiles.ContainsKey(transferFileInfo.Guid))
                return;

            lock (lockHelper)
            {
                runningNum--;
                this.transferFiles.Remove(transferFileInfo.Guid);
            }

            if(transferFileInfo.Status== TransferFileStatus.DownLoad)
                this.sendInvoke(transferFileInfo.Session, System.Text.Encoding.UTF8.GetBytes(BEGINTAG + "KILL|" + (success ? "1" : "0") + "|" + transferFileInfo.Guid + BEGINTAG));

            transferFileInfo.IsFail = !success;
            if (this.FinishTransferHandler != null) this.FinishTransferHandler(transferFileInfo);
            this.TransferWait();
        }

        //添加一个文件上传计划并尝试执行
        public void AddTransferFile(string filePath, string tag, Session session)
        {
            
            TransferFileInfo transferFileInfo = new TransferFileInfo();
            transferFileInfo.Status = TransferFileStatus.UpLoad;
            if (!File.Exists(filePath)) throw new FileNotFoundException(filePath);

            int i = filePath.LastIndexOf('\\');
            if (i == -1) throw new ArgumentException(filePath);

            transferFileInfo.FileDir = filePath.Substring(0, ++i);
            transferFileInfo.FileName = filePath.Substring(i);
            transferFileInfo.Tag = tag;
            transferFileInfo.FileStream = File.Open(transferFileInfo.FileDir + transferFileInfo.FileName, FileMode.Open, FileAccess.Read, FileShare.Read);
            transferFileInfo.Buffer = new byte[this.ReadSize];
            MD5CryptoServiceProvider get_md5 = new MD5CryptoServiceProvider();
            byte[] hash_byte = get_md5.ComputeHash(transferFileInfo.FileStream);
            transferFileInfo.MD5 = BitConverter.ToString(hash_byte).Replace("-", "");
            transferFileInfo.FileStream.Position = 0;
            transferFileInfo.Guid = Guid.NewGuid().ToString();
            transferFileInfo.FileSize = transferFileInfo.FileStream.Length;
            transferFileInfo.Session = session;
            transferFileInfo.TransferSize = 0;

            bool flag = false;
            if (this.AddingTransferHandler != null) this.AddingTransferHandler(transferFileInfo, ref flag);
            if (flag) return;

            this.transferFiles.Add(transferFileInfo.Guid, transferFileInfo);
            this.waitList.Enqueue(transferFileInfo.Guid);

            if (this.AddTransferHandler != null) this.AddTransferHandler(transferFileInfo);

            this.TransferWait();
        }
        public void AddTransferFile(string filePath, Session session)
        {
            AddTransferFile(filePath, null, session);
        }
    }

    public enum TransferFileStatus { UpLoad, DownLoad };

    public class TransferFileInfo
    {
        public string MD5 { get; set; }
        public string Guid { get; set; }
        public int BatchId { get; set; }
        public string FileDir { get; set; }
        public string FileName { get; set; }
        public string ToFileName { get; set; }
        public string TempFileDir { get; set; }
        public DateTime BeginTime { get; set; }
        public long FileSize { get; set; }
        public long TransferSize { get; set; }
        public TransferFileStatus Status { get; set; }
        public bool IsFail { get; set; }
        public byte[] Buffer { get; set; }
        internal FileStream FileStream { get; set; }
        public Session Session { get; set; }
        public string Tag { get; set; }
        public bool InProcess { get; set; }
        public string FileExt
        {
            get
            {
                if (string.IsNullOrEmpty(FileName)) return string.Empty;
                int i = FileName.LastIndexOf('.');
                return i == -1 ? string.Empty : FileName.Substring(i);
            }
        }
    }
}
